fix: make api:check pass across the workspace#1829
Conversation
…1525) Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com> Co-authored-by: u9g <jason.lernerman@livekit.io>
Agent.llmNode now returns ReadableStream<ChatChunk | string | FlushSentinel>, but the agent_v2 hook overrides and AgentHookAdapter still declared the narrower ChatChunk | string union, so passing super.llmNode as the fallback failed to type-check. Widen the override return types and the adapter's fallback/return signatures to include FlushSentinel. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Brian Yin <brian.yin@livekit.io> Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com> Co-authored-by: u9g <jason.lernerman@livekit.io>
Catch end-call close listener errors to avoid unhandled rejections during shutdown, and make public tool type guards return false for null inputs.
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
…egment (#1760) Co-authored-by: Cursor <cursoragent@cursor.com>
…#1698) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com> Co-authored-by: Toubat <brian.yin@livekit.io>
# Conflicts: # agents/src/voice/agent_activity.test.ts # agents/src/voice/generation.ts # agents/src/voice/generation_tts_timeout.test.ts
The test asserted an exact tick count ([0,1,2]) against real timers with a 5ms margin, which flakes on loaded CI runners. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: rosetta-livekit-bot[bot] <282703043+rosetta-livekit-bot[bot]@users.noreply.github.com>
Co-authored-by: Long Chen <longch1024@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: u9g <jason.lernerman@livekit.io>
Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: u9g <jason.lernerman@livekit.io>
api:check was broken for every package and never wired into CI. Fix it as a structural API gate. - Upgrade @microsoft/api-extractor 7.43.7 -> ^7.58.9. The old engine bundled TypeScript 5.4.2 and hard-errored on the `export * as ns` syntax in the core entry point; the new engine bundles TS 5.9.3 and supports it natively. - api-extractor-shared.json: bundle workspace packages so the extractor can resolve the `voice`/`llm` namespace re-exports plugins consume from @livekit/agents (previously failed with "SourceFile declaration not supported"); disable apiReport (this repo gitignores *.md and does not track etc/*.api.md reports) and silence the lint-style advice the repo does not adopt (release tags, forgotten exports, doc-comment placement, etc.). api:check now fails only on hard structural errors. - Add missing api-extractor.json for the bey and liveavatar plugins (they had the api:check script but no config). - Remove the two stale committed reports now that report comparison is off. Co-authored-by: Cursor <cursoragent@cursor.com>
|
| // re-exports (e.g. `voice`, `llm`) that plugins consume from `@livekit/agents`. | ||
| // Without this, API Extractor errors with "ts.SyntaxKind.SourceFile declaration | ||
| // which is not (yet?) supported" when a plugin references those namespaces. | ||
| "bundledPackages": ["@livekit/agents", "@livekit/agents-plugin-*"], |
There was a problem hiding this comment.
Thanks! 😄 Took a while to find — the older api-extractor just hard-errored on export * as ns, the newer one resolves it once the workspace packages are bundled.
| * public API type-checks and rolls up cleanly (not to diff a committed report). | ||
| */ | ||
| "enabled": true | ||
| "enabled": false |
There was a problem hiding this comment.
why wouldn't we want to enable this?
There was a problem hiding this comment.
Good question. I disabled it mainly because of how it interacts with bundledPackages: with the namespace bundling, each plugin report would inline all of @livekit/agents's API into its .api.md, so every plugin report becomes huge and churns on every core change. Combined with the repo gitignoring *.md (only 2 reports were ever force-added) and api:check not being in CI, it felt like the report wasn't really being used.
That said, I think it's genuinely valuable for the core @livekit/agents package — it doesn't bundle anything, so its report stays clean and gives a real public-API diff. Happy to enable it just for core (commit agents/etc/agents.api.md + a !etc/*.api.md gitignore exception) and keep plugins structural-only. Want me to do that?
There was a problem hiding this comment.
ok, I think it makes sense to stay disabled for now. I had opened a PR to fix the underlying issue of api-extractor with the namespace export here microsoft/rushstack#5810
Once that gets merged we can revert the bundledPackages workaround and re-enable here as well.
| // }, | ||
| // | ||
| // . . . | ||
| "logLevel": "none" |
There was a problem hiding this comment.
this seems a bit excessive with all the logs underneath also be setting to none.
What's your thinking behind it?
There was a problem hiding this comment.
Fair — it is heavy-handed. Two parts:
- The explicit per-message list isn't redundant with
default: api-extractor ships built-in per-message defaults (ae-forgotten-export,ae-missing-release-tag, etc. default towarning) that take precedence over the user'sdefaultentry, sodefault: nonealone doesn't silence them — I had to set each one explicitly (verified empirically). - You're right that it silences more than ideal. The release-tag family + unresolved-link/inheritdoc are conventions this repo deliberately doesn't use, so those are safe to drop. The one I'd rather keep is
ae-forgotten-export(catches types used in the public API but not exported) — but the core currently has ~4 (OutputSchema,AgentConstructor,PromptTemplate,UpdatePromptArgs), so keeping it as a warning means exporting those, which is a small public-API change.
Happy to trim to just the release-tag + link families and fix those 4 forgotten exports if you'd prefer the gate to stay meaningful — let me know.
There was a problem hiding this comment.
yeah, forgotten export sounds useful to keep
api:check needs each package's own dist/index.d.ts to exist, but it was wired to `^build` (dependency workspaces only), so on a fresh checkout without a prior `pnpm build` the gate failed with "mainEntryPointFilePath does not exist". Depend on the same-package `build` (which transitively pulls in `^build`) so `pnpm api:check` is reproducible from a clean tree. Co-authored-by: Cursor <cursoragent@cursor.com>
| "api:check": { | ||
| "cache": false, | ||
| "dependsOn": ["^build"] | ||
| "dependsOn": ["build"] |
There was a problem hiding this comment.
🟡 api:update task dependency not updated consistently with api:check
The PR changed api:check's dependsOn from ["^build"] to ["build"] to ensure the current package is built before API Extractor runs (it needs ./dist/index.d.ts). However, api:update at turbo.json:127 was left with the old ["^build"], which only builds upstream workspace dependencies — not the current package itself. Since api:update also runs api-extractor run and requires the current package's ./dist/index.d.ts, running turbo run api:update on a clean workspace will fail because the entry point file doesn't exist. The fix applied to api:check should also be applied to api:update for consistency.
Prompt for agents
In turbo.json, the api:check task was correctly updated from dependsOn: ["^build"] to dependsOn: ["build"] so that the current package is built before API Extractor runs (it needs ./dist/index.d.ts). The same change should be applied to the api:update task at line 127, which also runs api-extractor run and has the same dependency on the current package's build output. Change api:update's dependsOn from ["^build"] to ["build"].
Was this helpful? React with 👍 or 👎 to provide feedback.
| onUserToolStarted: () => { | ||
| if (!toolOutput.firstToolStartedFuture.done) { | ||
| toolOutput.firstToolStartedFuture.resolve(); | ||
| } |
There was a problem hiding this comment.
🔴 Tool-started signal never fires when all tool calls in a batch are duplicate-rejected, potentially hanging downstream waiters
The "first tool started" signal is never resolved (onUserToolStarted at agents/src/voice/generation.ts:1189) when the executor's duplicate check returns early (agents/src/voice/tool_executor.ts:220), so any downstream code awaiting that signal hangs indefinitely.
Impact: If an LLM emits a tool call that is duplicate-rejected (e.g. onDuplicate: 'reject' while the same tool is already running) and no other tool call in the batch starts normally, any coordination that awaits firstToolStartedFuture will hang forever.
Mechanism: duplicate-rejection bypasses onUserToolStarted callback
In the old code (removed at the equivalent of line 1053–1055), firstToolStartedFuture was resolved unconditionally before every tool execution:
if (!toolOutput.firstToolStartedFuture.done) {
toolOutput.firstToolStartedFuture.resolve();
}
In the new code, resolution is deferred to onUserToolStarted, which is passed into executor.execute() at agents/src/voice/generation.ts:1189-1192. Inside ToolExecutor.execute() at agents/src/voice/tool_executor.ts:216-220:
const duplicateResult = await this.checkDuplicate(...);
if (duplicateResult !== undefined) return duplicateResult;When checkDuplicate returns a rejection message, the method returns immediately without ever calling runTool (which is where onUserToolStarted?.() fires at agents/src/voice/tool_executor.ts:477). The firstToolStartedFuture is therefore never resolved for that tool call.
If this is the only tool call in the response (or all calls are rejected), toolOutput.firstToolStartedFuture remains pending.
Prompt for agents
The firstToolStartedFuture must be resolved even when a tool call is duplicate-rejected by the executor. The resolution was previously unconditional (before the executor existed), but now it only fires inside onUserToolStarted which requires runTool to be called.
Approach: After the executor.execute() call resolves (line 1198 in generation.ts, inside tracableToolExecution), check if firstToolStartedFuture is still pending and resolve it. Alternatively, resolve it inside the executor's execute() method right before returning the duplicate result (at tool_executor.ts:220), by calling onUserToolStarted() before the early return. The latter approach is cleaner since it keeps the signaling logic co-located with the execution path.
Was this helpful? React with 👍 or 👎 to provide feedback.
| const commitStrategy = serverVad === undefined || serverVad === null ? 'manual' : 'vad'; | ||
| const params = [ |
There was a problem hiding this comment.
🚩 ElevenLabs STT default commit strategy changed from 'vad' to 'manual'
At plugins/elevenlabs/src/stt.ts:653-654, the default commit strategy is now 'manual' when serverVad is undefined (the constructor default). Previously, the condition was this.#opts.serverVad === null ? 'manual' : 'vad', meaning undefined (unset) defaulted to 'vad'. Now both undefined and null map to 'manual'. This changes the default behavior for users who don't explicitly set serverVad — they'll get manual commit strategy instead of VAD-based. The test updates at lines 283 and 396-408 confirm this intentional change. Users relying on the old default VAD behavior without explicitly setting serverVad will need to pass serverVad: {} to restore it.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
pnpm api:checkwas failing for every package and was never wired into CI, so the breakage went unnoticed. This makes it a working structural API gate across the whole workspace (62/62 packages pass).Root causes were:
@microsoft/api-extractorwas years out of date (7.43.7, bundling TypeScript 5.4.2 while the repo uses 5.9.3). That engine hard-errored on theexport * as ns from '...'syntax inagents/src/index.ts.extends voice.AvatarSessionmakes the extractor follow@livekit/agents'sexport * as voicenamespace, which it couldn't resolve (ts.SyntaxKind.SourceFile declaration ... not supported).beyandliveavatarhad anapi:checkscript but noapi-extractor.json..gitignorehas*.md; only 2 reports were force-added; not in CI).Changes
@microsoft/api-extractorto^7.58.9across all packages (+ lockfile). New engine bundles TS 5.9.3 and supportsexport * as nsnatively.api-extractor-shared.json:bundledPackages: ["@livekit/agents", "@livekit/agents-plugin-*"]so namespace re-exports (voice,llm) resolve across package boundaries.apiReport.enabled: false— this repo doesn't tracketc/*.api.mdreports, soapi:checkvalidates that the public API type-checks and rolls up cleanly rather than diffing a committed report.ae-missing-release-tag,ae-forgotten-export,ae-internal-missing-underscore, doc/link advice, …). Hard errors (unresolvable types, the namespace error, missing entry points) still fail.api-extractor.jsonfor thebeyandliveavatarplugins.perplexity,soniox) now that report comparison is off.No source files are touched — this is purely a dev-tooling/config fix.
Test plan
pnpm api:check→ 62/62 successfulapi:check's^buildand succeedsapi:checkinto CI now that it passesNotes
devDependency(@microsoft/api-extractor) and dev config, so there's nothing user-facing to release.1.5.0(the active integration branch), notmain.